/*
Taylor Seale, Justin Bartlett, Ka Hin Lee
December 5, 2013
Project 4 - P2P Server
*/

// networking ish
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// C++ data structures ish
#include <vector>
#include <map>
#include <utility>

// threading
#include <pthread.h>

// other libraries
#include <stdio.h>
#include <string>
#include <cstdlib>
#include <cstring>
#include <ifaddrs.h>
#include <iostream>
#include <netdb.h> 

#define PORT 9979

using namespace std;

// pair containing client host/port information
// hostname is first & port number is second
typedef pair <string, int> client_info; 
// vector of all clients


//forward declarations
void checkInput (int argc);
void* handle_connection(void* param);


// initialize the data structure that holds clients ip address / port
vector<client_info> client_list;


int main (int argc, char**argv)
{
   checkInput(argc);
      
   // declare variables for networking stuff 
   int sock;
   int new_sock;
   struct sockaddr_in servaddr;
  // struct sockaddr_in client_addr;
   socklen_t len;
   
   //initialize values
   bzero(&servaddr,sizeof(servaddr));
   servaddr.sin_family = AF_INET;
   servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
   servaddr.sin_port=htons(PORT);
   
   
   // set up passive open
   if (( sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
   {
	  perror ("Socket Error");
	  exit(1);
   }
   
   char test=' ';
   char *temp2=&test;
   if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,temp2,sizeof(int)) < 0)
   {
	  perror("setsockopt");
   }
   
   // bind and check for errors
   if ((bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0 )
   {
	  perror ("Bind Error");
	  exit(1);
   }
   //listen
   if (listen (sock, 1024) < 0)
   {
	  cout << "Error on list" << endl;
   }
   
   //loop to recieve input
   while (1)
   {
	  if ((new_sock = accept(sock, (struct sockaddr *)&servaddr, &len)) < 0)
	  {
		 perror ("Accept Error");
		 exit(1);
	  }
	 
	  //spawn a new thread to handle this connection
	  int *param = (int *) malloc (sizeof(*param));
	  *param = new_sock;
	  // create a new thread for the accepted connection
	  pthread_t child_pid;
	  pthread_create(&child_pid, NULL, handle_connection, param);
	  
   }
   
   //close sockets???
   //close(sock);
   return 0;
}

void* handle_connection(void* param)
{
   // store socket information for thread connection locally
   int *fdptr = (int *) param;
   int client_local = *fdptr; 
   free (fdptr);
   
   socklen_t len;
   struct sockaddr_in servaddr,client_addr;
   //if (read(client_local,(struct client_data*)&data1,sizeof(data1))<0)
   while(1)
   {
	  
	  len = sizeof(client_addr);
   
	  // determine the request type from the client
	  // 0 is a new connection (need to add information to files map)
	  // 1 is a search request
	  short request_type;
	  if (recvfrom(client_local,&request_type,sizeof(short),0,(struct sockaddr *)&client_addr,&len)<= 0)
	  {
		 close(client_local);
		 break;
	  }
	  request_type=ntohs(request_type);
	  // determine server action based on request type
	  if (request_type==0)
	  {
		 // get IP address length from client
		 short ip_length;
		 if (recv(client_local,&ip_length,sizeof(short),0) < 0)
		 {
			cout << "Error: recv failure -> Ip address" << endl;
		 }
		 ip_length=ntohs(ip_length);
		 char ip_address[100];
		 bzero(ip_address,100);
		 // get IP address value
		 if (recv(client_local,&ip_address,ip_length,0) < 0)
		 {
			cout << "Error: recv failure -> Ip address" << endl;			   
		 }
		 cout << "New connection from " << ip_address << " @ PORT: " << 9976 << endl;
		 
		 //check if ip address already in data structure
		 int alreadyExists=0;
		 for (unsigned int i = 0; i < client_list.size(); i++)
		 {
			if ((client_list[i]).first == ip_address)
			{
			   alreadyExists=1;
			}
		 }
		 //if it does not, then add it
		 if (alreadyExists == 0)
		 {
			client_list.push_back(make_pair(ip_address,9976));			
		 }
	  }
	  else if (request_type == 1)
	  {
		 //send the number of machines connected
		 short num_clients = client_list.size();
		 if (sendto(client_local,&num_clients, sizeof(short),0,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
		 {
			printf("Error: sendto failure -> Number of clients (Search)\n");
		 }
		 //cout << "num clients: " << num_clients << endl;
		 //for each machine connected send info
		 for (unsigned int i=0; i < client_list.size(); i++)
		 {

			short ip_length = strlen(client_list[i].first.c_str());
   			string temp = client_list[i].first;
			char ip_address[100];
			strcpy(ip_address,temp.c_str()); 
			//cout << "ip address: " << ip_address << endl;
			//cout << "ip address length: " << ip_length << endl;
			if (sendto(client_local,&ip_length, sizeof(short),0,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
			{
			   printf("Error: sendto failure -> Number of clients (Search)\n");
			}
			if (sendto(client_local,&ip_address, ip_length,0,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
			{
			   printf("Error: sendto failure -> Number of clients (Search)\n");
			}
			if (sendto(client_local,&client_list[i].second, sizeof(short),0,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
			{
			   printf("Error: sendto failure -> Number of clients (Search)\n");
			}
		 		
		 }
		 
	  }
	  else
	  {
		 cout << "Invalid request" << endl;
	  }
   }

	
	//cleanup
	//close(client_local); // close the socket for this thread
	pthread_exit(NULL); // exit this thread
	return 0;
}


void checkInput (int argc)
{
   // make sure the correct amount of arguments are given
   if (argc != 1)
   {
	  cout << "Incorrect Number of Arguments\n";
	  cout << "usage: P2Pserver\n";
	  exit(1);
   }
}
